package fr.kptn.api.readability;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.util.EntityUtils;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;

import fr.kptn.api.readability.exceptions.AuthorizationRequiredException;
import fr.kptn.api.readability.exceptions.ForbiddenException;
import fr.kptn.api.readability.exceptions.InternalServerException;
import fr.kptn.api.readability.exceptions.NotFoundException;
import fr.kptn.api.readability.exceptions.ReadabilityException;
import fr.kptn.api.readability.structs.reader.Collections;
import fr.kptn.api.readability.utils.DateFormatter;
import fr.kptn.api.readability.utils.deserializers.BookmarksOrderDeserializer;
import fr.kptn.api.readability.utils.deserializers.DateDeserializer;
import fr.kptn.api.readability.utils.enums.BookmarksOrder;

public abstract class APICommunicator {

	/**
	 * Do a GET request at the specified URI and returns the HTTP response
	 * returned. <br/>
	 * <br/>
	 * 
	 * @param uri
	 *            the URI of the GET request
	 * @return the HTTP response
	 * @throws ReadabilityException
	 */
	public abstract HttpResponse doGet(String uri) throws ReadabilityException;

	/**
	 * Do a POST request at the specified URI and returns the HTTP response
	 * returned. <br/>
	 * <br/>
	 * 
	 * @param uri
	 *            the URI of the POST request
	 * @param params
	 *            the parameters for the POST request (they will be "posted" in
	 *            the body of the POST request)
	 * @return the HTTP response
	 * @throws ReadabilityException
	 */
	public abstract HttpResponse doPost(String uri, Map<String, String> params)
			throws ReadabilityException;

	/**
	 * Do a PUT request at the specified URI and returns the HTTP response
	 * returned. <br/>
	 * <br/>
	 * 
	 * @param uri
	 *            the URI of the PUT request
	 * @return the HTTP response
	 * @throws ReadabilityException
	 */
	public abstract HttpResponse doPut(String uri) throws ReadabilityException;

	/**
	 * Do a HEAD request at the specified URI and returns the HTTP response
	 * returned. <br/>
	 * <br/>
	 * 
	 * @param uri
	 *            the URI of the HEAD request
	 * @return the HTTP response
	 * @throws ReadabilityException
	 */
	public abstract HttpResponse doHead(String uri) throws ReadabilityException;

	/**
	 * Do a DELETE request at the specified URI and returns the HTTP response
	 * returned. <br/>
	 * <br/>
	 * 
	 * @param uri
	 *            the URI of the DELETE request
	 * @return the HTTP response
	 * @throws ReadabilityException
	 */
	public abstract HttpResponse doDelete(String uri)
			throws ReadabilityException;

	private static Gson GSON_HELPER;

	/**
	 * Static accessor for the helper used to parse/build JSON
	 * 
	 * @return
	 */
	public static Gson getGsonHelper() {
		if (GSON_HELPER == null) {
			GsonBuilder lGsonBuilder = new GsonBuilder();
			lGsonBuilder
					.registerTypeAdapter(Date.class, new DateDeserializer());
			lGsonBuilder.registerTypeAdapter(BookmarksOrder.class,
					new BookmarksOrderDeserializer());
			GSON_HELPER = lGsonBuilder.create();
		}

		return GSON_HELPER;
	}

	/**
	 * Check if the response can be handle or if there has been an error
	 * 
	 * @param response
	 *            the HTTP response
	 * @param context
	 *            the context of the call which issued this response
	 * @return true if the response can be handled, false otherwise
	 * @throws ReadabilityException
	 */
	public boolean canHandleResponse(HttpResponse response, String context)
			throws ReadabilityException {
		int statusCode = response.getStatusLine().getStatusCode();
		if (statusCode == HttpURLConnection.HTTP_OK
				|| statusCode == HttpURLConnection.HTTP_NO_CONTENT
				|| statusCode == HttpURLConnection.HTTP_ACCEPTED
				|| statusCode == HttpURLConnection.HTTP_CREATED) {
			return true;
		} else if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
			throw new AuthorizationRequiredException(
					"You try to access the resource (" + context
							+ ") but you need to be logged to do this");
		} else if (statusCode == HttpURLConnection.HTTP_FORBIDDEN) {
			throw new ForbiddenException(
					"You did not have the right ro access the resource ("
							+ context + ")");
		} else if (statusCode == HttpURLConnection.HTTP_NOT_FOUND) {
			throw new NotFoundException("The resource (" + context
					+ ") cannot be found");
		} else if (statusCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
			throw new InternalServerException(
					"There has been an error on Readability servers when accessing the resource ("
							+ context + ")");
		} else if (statusCode == HttpURLConnection.HTTP_CONFLICT) {
			throw new InternalServerException(
					"There has been a conflict for the resource (" + context
							+ "). The resource already exists");
		}

		return false;
	}

	/**
	 * Transform the response into a string
	 * 
	 * @param response
	 *            the HTTP response
	 * @return the HTTP response as a string
	 * @throws ReadabilityException
	 */
	public String getResponseAsString(HttpResponse response)
			throws ReadabilityException {
		try {
			return EntityUtils.toString(response.getEntity(), "UTF-8");
		} catch (ParseException e) {
			throw new ReadabilityException(
					"Parsing error during the processing of an HTTP response",
					e);
		} catch (IOException e) {
			throw new ReadabilityException(
					"IO error during the processing of an HTTP response", e);
		}
	}

	/**
	 * Transform the response into an structure using GSON
	 * 
	 * @param response
	 *            the HTTP response
	 * @param returnClass
	 *            the type of the returned object
	 * @return
	 * @throws ReadabilityException
	 */
	public <T> T getResponseAsStruct(HttpResponse response, Class<T> returnClass)
			throws ReadabilityException {
		try {
			String result = EntityUtils.toString(response.getEntity(), "UTF-8");
			return (T) getGsonHelper().fromJson(result, returnClass);
		} catch (ParseException e) {
			throw new ReadabilityException(
					"Parsing error during the processing of an HTTP response",
					e);
		} catch (IOException e) {
			throw new ReadabilityException(
					"IO error during the processing of an HTTP response", e);
		} catch (JsonSyntaxException e) {
			throw new ReadabilityException(
					"JSon syntax error during the processing of an HTTP response",
					e);
		}
	}

	/**
	 * Transform the response into an structure (as list) using GSON
	 * 
	 * @param response
	 *            the HTTP response
	 * @param innerCollectionClass
	 *            the type of the object inside the list
	 * @return
	 * @throws ReadabilityException
	 */
	public <T, V> Collection<T> getResponseAsList(HttpResponse response,
			Class<T> innerCollectionClass, Class<V> collectionClass)
			throws ReadabilityException {
		try {
			String result = EntityUtils.toString(response.getEntity(), "UTF-8");
			return ((Collections<T>) getGsonHelper().fromJson(result,
					collectionClass)).getCollection();
		} catch (ParseException e) {
			throw new ReadabilityException(
					"Parsing error during the processing of an HTTP response",
					e);
		} catch (IOException e) {
			throw new ReadabilityException(
					"IO error during the processing of an HTTP response", e);
		} catch (JsonSyntaxException e) {
			throw new ReadabilityException(
					"JSon syntax error during the processing of an HTTP response",
					e);
		}
	}

	protected void addBooleanParam(StringBuffer urlSB, String paramName,
			boolean paramValue) throws UnsupportedEncodingException {
		addParam(urlSB, paramName, paramValue ? "1" : "0");
	}

	protected void addIntegerParam(StringBuffer urlSB, String paramName,
			Integer paramValue) throws UnsupportedEncodingException {
		addParam(urlSB, paramName, String.valueOf(paramValue));
	}

	protected void addDateParam(StringBuffer urlSB, String paramName,
			Date paramValue) throws UnsupportedEncodingException,
			java.text.ParseException {
		if (paramValue != null) {
			addParam(urlSB, paramName, DateFormatter.formatDate(paramValue));
		}
	}

	protected void addParam(StringBuffer urlSB, String paramName,
			String paramValue) throws UnsupportedEncodingException {
		if (paramValue != null && !"".equals(paramValue)) {
			urlSB.append("&").append(paramName).append("=")
					.append(URLEncoder.encode(paramValue, "UTF-8"));
		}
	}
}
